﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Web.Script.Serialization;
using System.Linq;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using System.Text.Json;
using System.Net.Http;

namespace StackingDemo
{
    public partial class Main : Form
    {
        //来板顺序
        const int PalletWidth = 2440;
        const int PalletHeight = 1220;
        private int currentDisplayLayer = 0;
        private const int LayerHeight = 500; // 假设层高500
        private bool showAllLayers = false;     // 是否显示所有层叠加
                                                // 板与板之间的最小间距（mm）
        private int InterBoardGap = 40;  // 可改成从 UI 读取

        // （可选）托盘四边留边，默认 0 表示不强制
        private int EdgeMargin = 0;
        // 如果表格没有“高度/厚度”列，就用它
        private const double DefaultBoardHeight = 18;


        // 左上角 <-> 中心点 互转（Length=沿X，Width=沿Y；都是脚印尺寸）
        private static (int x, int y) TopLeftFromCenter(double cx, double cy, int length, int width)
        {
            int x = (int)Math.Round(cx - length / 2.0);
            int y = (int)Math.Round(cy - width / 2.0);
            return (x, y);
        }
        private static (double cx, double cy) CenterFromTopLeft(int x, int y, int length, int width)
        {
            double cx = x + length / 2.0;
            double cy = y + width / 2.0;
            return (cx, cy);
        }




        private readonly object _lockBoards = new object();
        List<Board> boards = new List<Board>();

        Dictionary<string, List<PlacedBoard>> palletPlacements = new Dictionary<string, List<PlacedBoard>>();

        private HttpListener listener;
        private Thread listenerThread;

        public Main()
        {
            InitializeComponent();

            // 新增DataGridView列初始化（如果未在设计器设置）
            InitDataGridView();


            // 布局设置
            tableLayoutPanel1.ColumnCount = 2;
            tableLayoutPanel1.RowCount = 1;
            tableLayoutPanel1.Dock = DockStyle.Fill;
            for (int i = 0; i < 2; i++)
                tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F));
            tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));

            btnLoadStack.Click += BtnLoadStack_Click;
            showAllLayers = chkShowAllLayers.Checked;
            chkShowAllLayers.CheckedChanged += ChkShowAllLayers_CheckedChanged;
            nudLayer.ValueChanged += NudLayer_ValueChanged;


            StartHttpListener();
        }

        private void InitDataGridView()
        {
            dgvStackInfo.Columns.Clear();
            dgvStackInfo.Columns.Add("Group", "托盘组");
            dgvStackInfo.Columns.Add("Barcode", "条码");
            dgvStackInfo.Columns.Add("X", "X");
            dgvStackInfo.Columns.Add("Y", "Y");
            dgvStackInfo.Columns.Add("Length", "长度");
            dgvStackInfo.Columns.Add("Width", "宽度");

            // 旋转列设置为下拉框并指定为字符串类型
            DataGridViewComboBoxColumn rotatedColumn = new DataGridViewComboBoxColumn
            {
                Name = "Rotated",
                HeaderText = "旋转",
                ValueType = typeof(string) // 明确设置为字符串类型
            };
            rotatedColumn.Items.Add("是");
            rotatedColumn.Items.Add("否");
            dgvStackInfo.Columns.Add(rotatedColumn);

            dgvStackInfo.Columns.Add("Layer", "层数");

            // 设置可编辑
            dgvStackInfo.ReadOnly = false;
            dgvStackInfo.AllowUserToAddRows = false;
            dgvStackInfo.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
            dgvStackInfo.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;

            // 设置其他列的单元格类型
            dgvStackInfo.Columns["X"].ValueType = typeof(int);
            dgvStackInfo.Columns["Y"].ValueType = typeof(int);
            dgvStackInfo.Columns["Length"].ValueType = typeof(int);
            dgvStackInfo.Columns["Width"].ValueType = typeof(int);
            dgvStackInfo.Columns["Layer"].ValueType = typeof(int);
            dgvStackInfo.Columns["Group"].ReadOnly = true; // 托盘组通常不可编辑
            dgvStackInfo.Columns["Barcode"].ReadOnly = true; // 订单号通常不可编辑

            // 添加单元格结束编辑事件
            dgvStackInfo.CellEndEdit += DgvStackInfo_CellEndEdit;
        }
        private void BtnExportJson_Click(object sender, EventArgs e)
        {
            var panels = CollectPanelsFromGrid();
            if (panels.Count == 0)
            {
                MessageBox.Show("表格没有可导出的数据。");
                return;
            }

            var sfd = new SaveFileDialog
            {
                Filter = "JSON 文件 (*.json)|*.json",
                FileName = $"panels_{DateTime.Now:yyyyMMdd_HHmmss}.json"
            };
            if (sfd.ShowDialog() != DialogResult.OK) return;

            var opts = new JsonSerializerOptions
            {
                WriteIndented = true,
                Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
            };

            string json = JsonSerializer.Serialize(panels, opts);
            File.WriteAllText(sfd.FileName, json, Encoding.UTF8);

            MessageBox.Show($"已导出 {panels.Count} 条到：\n{sfd.FileName}", "导出成功",
                MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        private void DgvStackInfo_CellEndEdit(object sender, DataGridViewCellEventArgs e)
        {
            if (e.RowIndex < 0) return;
            var grid = dgvStackInfo;
            var row = grid.Rows[e.RowIndex];

            int Col(params string[] keys)
            {
                for (int i = 0; i < grid.Columns.Count; i++)
                {
                    var col = grid.Columns[i];
                    string name = col.Name?.Trim() ?? "";
                    string head = col.HeaderText?.Trim() ?? "";
                    foreach (var k in keys)
                    {
                        if (string.Equals(name, k, StringComparison.OrdinalIgnoreCase) ||
                            string.Equals(head, k, StringComparison.OrdinalIgnoreCase))
                            return i;
                    }
                }
                return -1;
            }

            int cGroup = Col("Group", "托盘组");
            int cOrder = Col("Barcode", "订单号");
            int cCX = Col("X", "中心X", "x");
            int cCY = Col("Y", "中心Y", "y");
            int cLength = Col("Length", "长度");
            int cWidth = Col("Width", "宽度");
            int cRotated = Col("Rotated", "旋转");
            int cLayer = Col("Layer", "层数");

            string group = cGroup >= 0 ? Convert.ToString(row.Cells[cGroup].Value)?.Trim() : "0";
            string order = cOrder >= 0 ? Convert.ToString(row.Cells[cOrder].Value)?.Trim() : null;
            if (string.IsNullOrEmpty(order))
            {
                MessageBox.Show("缺少订单号。", "输入错误", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                RefreshDataGridView();
                return;
            }

            // 允许中心坐标为小数；尺寸/层数为整数
            bool TryDouble(int col, out double v) { v = 0; return col >= 0 && double.TryParse(Convert.ToString(row.Cells[col].Value)?.Trim(), out v); }
            bool TryInt(int col, out int v) { v = 0; return col >= 0 && int.TryParse(Convert.ToString(row.Cells[col].Value)?.Trim(), out v); }

            if (!TryDouble(cCX, out double cx) || !TryDouble(cCY, out double cy) ||
                !TryInt(cLength, out int length) || !TryInt(cWidth, out int width) ||
                !TryInt(cLayer, out int layer))
            {
                MessageBox.Show("请填写合法的数值（中心X/中心Y 支持小数；长度/宽度/层数为整数）。", "输入错误", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                RefreshDataGridView();
                return;
            }

            // 旋转仅信息记录；长度/宽度是脚印尺寸
            bool rotated = false;
            if (cRotated >= 0)
            {
                var rotStr = Convert.ToString(row.Cells[cRotated].Value)?.Trim();
                rotated = rotStr == "是" ||
                          rotStr?.Equals("true", StringComparison.OrdinalIgnoreCase) == true ||
                          rotStr?.Equals("yes", StringComparison.OrdinalIgnoreCase) == true;
            }

            // 中心 -> 左上角
            var (x, y) = TopLeftFromCenter(cx, cy, length, width);

            // —— 边界检查（仍按左上角+脚印尺寸）——
            if (x < 0 || y < 0 || x + length > PalletWidth || y + width > PalletHeight)
            {
                MessageBox.Show("放置越界：中心坐标导致板件超出托盘范围。", "越界", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                RefreshDataGridView();
                return;
            }

            // —— 间距/重叠检查（用“膨胀矩形相交”，与自动排版一致）——
            lock (_lockBoards)
            {
                if (!palletPlacements.TryGetValue(group, out var list))
                {
                    list = new List<PlacedBoard>();
                    palletPlacements[group] = list;
                }

                RectangleF InflateRectLocal(RectangleF r, float d) => new RectangleF(r.X - d, r.Y - d, r.Width + 2 * d, r.Height + 2 * d);
                RectangleF InterRect(RectangleF a, RectangleF b)
                {
                    float x1 = Math.Max(a.Left, b.Left);
                    float y1 = Math.Max(a.Top, b.Top);
                    float x2 = Math.Min(a.Right, b.Right);
                    float y2 = Math.Min(a.Bottom, b.Bottom);
                    return (x2 <= x1 || y2 <= y1) ? RectangleF.Empty : new RectangleF(x1, y1, x2 - x1, y2 - y1);
                }

                var newRect = new RectangleF(x, y, length, width);
                var newInfl = InflateRectLocal(newRect, InterBoardGap / 2f);

                foreach (var other in list)
                {
                    if (other.Layer != layer) continue;
                    if (string.Equals(other.Barcode, order, StringComparison.OrdinalIgnoreCase)) continue;

                    var oRect = new RectangleF(other.X, other.Y, other.Length, other.Width);
                    var oInfl = InflateRectLocal(oRect, InterBoardGap / 2f);

                    if (!InterRect(newInfl, oInfl).IsEmpty)
                    {
                        MessageBox.Show($"与条码 {other.Barcode} 的安全间距不足（< {InterBoardGap} mm）。",
                            "间距不足", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                        RefreshDataGridView();
                        return;
                    }
                }

                // —— 通过校验：更新模型（同层同订单只保留一条）——
                var old = list.FirstOrDefault(b => b.Layer == layer && string.Equals(b.Barcode, order, StringComparison.OrdinalIgnoreCase));
                if (old != null) list.Remove(old);

                list.Add(new PlacedBoard(order, x, y, length, width, rotated, layer));
            }

            // 刷新（会把 X/Y 显示为中心X/中心Y）
            RefreshPalletDisplay();
        }

        private (double cx, double cy) GetBoardCenter(PlacedBoard pb)
        {
            return (pb.X + pb.Length / 2.0, pb.Y + pb.Width / 2.0);
        }
        
        // 例：导出行
        private Dictionary<string, object> ToRobotRow(PlacedBoard pb)
        {
            var (cx, cy) = GetBoardCenter(pb);
            return new Dictionary<string, object>
            {
                ["Barcode"] = pb.Barcode,
                ["CenterX"] = Math.Round(cx, 3),
                ["CenterY"] = Math.Round(cy, 3),
                ["Length"] = pb.Length,
                ["Width"] = pb.Width,
                ["Rotated"] = pb.Rotated,
                ["Layer"] = pb.Layer,
            };
        }



        // 在 Form1 类中添加此方法
        /// <summary>
        /// 刷新所有托盘面板的绘制
        /// </summary>
        private void RefreshPalletDisplay()
        {
            // 遍历所有托盘面板并触发重绘
            foreach (Control ctl in tableLayoutPanel1.Controls)
            {
                if (ctl is Panel palletPanel)
                {
                    palletPanel.Invalidate(); // 触发 Paint 事件，重新绘制面板
                }
            }
        }

        private void NudLayer_ValueChanged(object sender, EventArgs e)
        {
            currentDisplayLayer = (int)nudLayer.Value;

            foreach (Control ctl in tableLayoutPanel1.Controls)
            {
                if (ctl is Panel pnl)
                    pnl.Invalidate();
            }

            RefreshDataGridView();
        }

        private void ChkShowAllLayers_CheckedChanged(object sender, EventArgs e)
        {
            showAllLayers = chkShowAllLayers.Checked;

            foreach (Control ctl in tableLayoutPanel1.Controls)
            {
                if (ctl is Panel pnl)
                    pnl.Invalidate();
            }

            RefreshDataGridView();
        }

        private void DisplayAllPallets()
        {
            tableLayoutPanel1.Controls.Clear();

            int index = 0;
            foreach (var kv in palletPlacements)
            {
                Panel palletPanel = new Panel
                {
                    Dock = DockStyle.Fill,
                    BorderStyle = BorderStyle.FixedSingle,
                    Margin = new Padding(5),
                    BackColor = Color.White,
                    Tag = kv.Key
                };

                palletPanel.Paint += PalletPanel_Paint;
                palletPanel.MouseMove += palletPanel_MouseMove;  
                palletPanel.Click += PalletPanel_Click;

                tableLayoutPanel1.Controls.Add(palletPanel, index, 0);

                index++;
                if (index >= 5) break;
            }

            RefreshDataGridView();
        }


        private void RefreshDataGridView()
        {
            try
            {
                //委托更新
                this.Invoke(new Action(() =>
                {
                    dgvStackInfo.SuspendLayout();
                    dgvStackInfo.Rows.Clear();

                    // 可选：把表头改成中心坐标语义
                    if (dgvStackInfo.Columns.Contains("X")) dgvStackInfo.Columns["X"].HeaderText = "中心X";
                    if (dgvStackInfo.Columns.Contains("Y")) dgvStackInfo.Columns["Y"].HeaderText = "中心Y";

                    foreach (var kv in palletPlacements)
                    {
                        string group = kv.Key;
                        foreach (var pb in kv.Value.OrderBy(p => p.Layer))
                        {
                            var (cx, cy) = CenterFromTopLeft(pb.X, pb.Y, pb.Length, pb.Width);

                            int row = dgvStackInfo.Rows.Add();
                            var r = dgvStackInfo.Rows[row];
                            r.Cells["Group"].Value = group;
                            r.Cells["Barcode"].Value = pb.Barcode;
                            r.Cells["X"].Value = cx.ToString("0.###");   // 中心X
                            r.Cells["Y"].Value = cy.ToString("0.###");   // 中心Y
                            r.Cells["Length"].Value = pb.Length;
                            r.Cells["Width"].Value = pb.Width;
                            r.Cells["Rotated"].Value = pb.Rotated ? "是" : "否";
                            r.Cells["Layer"].Value = pb.Layer;
                        }
                    }
                    dgvStackInfo.ResumeLayout();
                }));
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            
           
        }




        private void StartHttpListener()
        {
            listener = new HttpListener();
            listener.Prefixes.Add("http://localhost:5000/api/boards/");
            listener.Start();

            listenerThread = new Thread(() =>
            {
                try
                {
                    while (listener.IsListening)
                    {
                        var context = listener.GetContext();
                        ProcessRequest(context);
                    }
                }
                catch
                {
                    // 关闭时会抛异常，忽略
                }
            });
            listenerThread.IsBackground = true;
            listenerThread.Start();
        }

        private void ProcessRequest(HttpListenerContext context)
        {
            if (context.Request.HttpMethod != "POST")
            {
                context.Response.StatusCode = 405;
                context.Response.Close();
                return;
            }

            try
            {
                using (var reader = new StreamReader(context.Request.InputStream, context.Request.ContentEncoding))
                {
                    string json = reader.ReadToEnd();

                    var serializer = new JavaScriptSerializer();
                    List<Board> receivedBoards = new List<Board>();
                    List<SendPanelInfo> sendPanelInfo = serializer.Deserialize<List<SendPanelInfo>>(json);
                    foreach (var info in sendPanelInfo)
                    {
                        Board board = new Board(info.Barcode, (int)info.Length, (int)info.Width, info.Placement + "");
                        receivedBoards.Add(board);
                    }
                    if (receivedBoards == null || receivedBoards.Count == 0)
                    {
                        context.Response.StatusCode = 400;
                        WriteResponse(context, "无效的小板数据");
                        return;
                    }

                    lock (_lockBoards)
                    {
                        boards.AddRange(receivedBoards);
                        //GroupAndStackBoards();
                        // 解析出 boards 后，按 Group 分组增量放置
                        var byGroup = boards.GroupBy(b => b.Group ?? "0");
                        foreach (var g in byGroup)
                            AccumulateStackForGroup(g.Key, g.ToList(), allowNewLayers: true);

                    }

                    this.BeginInvoke(new Action(() =>
                    {
                        DisplayAllPallets();
                    }));

                    context.Response.StatusCode = 200;
                    WriteResponse(context, $"成功接收 {receivedBoards.Count} 个小板信息");
                }
            }
            catch (Exception ex)
            {
                context.Response.StatusCode = 500;
                WriteResponse(context, "服务器错误: " + ex.Message);
            }
        }


        private void WriteResponse(HttpListenerContext context, string message)
        {
            var buffer = Encoding.UTF8.GetBytes(message);
            context.Response.ContentLength64 = buffer.Length;
            context.Response.OutputStream.Write(buffer, 0, buffer.Length);
            context.Response.OutputStream.Close();
        }

        private void BtnLoadStack_Click(object sender, EventArgs e)
        {
            string csvPath = @"E:\1工作文件\意大利Arken\group.csv";

            if (!File.Exists(csvPath))
            {
                MessageBox.Show("CSV文件不存在：" + csvPath);
                return;
            }

            if (!LoadFromCsv(csvPath))
            {
                MessageBox.Show("CSV文件读取失败或格式不正确");
                return;
            }

            lock (_lockBoards)
            {
                GroupAndStackBoards();
            }

            DisplayAllPallets();
        }

        private bool LoadFromCsv(string path)
        {
            try
            {
                lock (_lockBoards)
                {
                    boards.Clear();

                    using (var reader = new StreamReader(path))
                    {
                        string headerLine = reader.ReadLine();
                        while (!reader.EndOfStream)
                        {
                            string line = reader.ReadLine();
                            if (string.IsNullOrWhiteSpace(line))
                                continue;

                            var parts = line.Split(',');

                            if (parts.Length < 7)
                                continue;

                            string order = parts[0];
                            if (!int.TryParse(parts[2], out int length))
                                continue;
                            if (!int.TryParse(parts[3], out int width))
                                continue;

                            string group = parts[6].Trim();
                            if (string.IsNullOrEmpty(group))
                                group = "默认组";

                            boards.Add(new Board(order, length, width, group));
                        }
                    }
                }
                return true;
            }
            catch
            {
                return false;
            }
        }

        // 在某个 freeRects 集合里，为单块板寻找最佳落点
        private bool TryFindBestPlacement(List<RectangleF> freeRects, Board b, out PlaceCandidate best, out int freeRectIndex)
        {
            best = null; freeRectIndex = -1;

            var orientations = new[]
            {
        new { Rot=false, L=(float)b.Length, W=(float)b.Width  },
        new { Rot=true,  L=(float)b.Width,  W=(float)b.Length }
    };

            for (int frIdx = 0; frIdx < freeRects.Count; frIdx++)
            {
                var fr = freeRects[frIdx];

                foreach (var o in orientations)
                {
                    if (o.L > fr.Width || o.W > fr.Height) continue;

                    Anchor[] anchors = { Anchor.TopLeft, Anchor.TopRight, Anchor.BottomLeft, Anchor.BottomRight };
                    foreach (var anchor in anchors)
                    {
                        float px, py;
                        switch (anchor)
                        {
                            case Anchor.TopLeft: px = fr.X; py = fr.Y; break;
                            case Anchor.TopRight: px = fr.Right - o.L; py = fr.Y; break;
                            case Anchor.BottomLeft: px = fr.X; py = fr.Bottom - o.W; break;
                            default: /*BottomRight*/ px = fr.Right - o.L; py = fr.Bottom - o.W; break;
                        }

                        // 在 freeRect 内
                        if (px < fr.X - 0.01f || py < fr.Y - 0.01f ||
                            px + o.L > fr.Right + 0.01f || py + o.W > fr.Bottom + 0.01f)
                            continue;

                        // 全局托盘边界保护
                        if (px < 0 || py < 0 || px + o.L > PalletWidth || py + o.W > PalletHeight)
                            continue;

                        var cand = new PlaceCandidate
                        {
                            FreeRectIndex = frIdx,
                            FreeRect = fr,
                            Rotated = o.Rot,
                            Anchor = anchor,
                            X = px,
                            Y = py,
                            W = o.L,
                            H = o.W
                        };
                        cand.Score = ScoreCandidate(cand);

                        // 你的平分规则（角数>贴边长度>距边距离>更靠上/左）
                        if (best == null || cand.Score < best.Score - 1e-3f)
                        {
                            best = cand; freeRectIndex = frIdx;
                        }
                        else if (Math.Abs(cand.Score - best.Score) <= 1e-3f)
                        {
                            bool cl = Math.Abs(cand.X - 0f) < 0.01f;
                            bool ct = Math.Abs(cand.Y - 0f) < 0.01f;
                            bool cr = Math.Abs((cand.X + cand.W) - PalletWidth) < 0.01f;
                            bool cb = Math.Abs((cand.Y + cand.H) - PalletHeight) < 0.01f;
                            int cCorners = (cl && ct ? 1 : 0) + (cr && ct ? 1 : 0) + (cl && cb ? 1 : 0) + (cr && cb ? 1 : 0);
                            float cContact = (cl ? cand.H : 0f) + (cr ? cand.H : 0f) + (ct ? cand.W : 0f) + (cb ? cand.W : 0f);
                            float cEdgeDist = Math.Min(cand.X, PalletWidth - (cand.X + cand.W))
                                            + Math.Min(cand.Y, PalletHeight - (cand.Y + cand.H));

                            bool bl = Math.Abs(best.X - 0f) < 0.01f;
                            bool bt = Math.Abs(best.Y - 0f) < 0.01f;
                            bool br = Math.Abs((best.X + best.W) - PalletWidth) < 0.01f;
                            bool bb = Math.Abs((best.Y + best.H) - PalletHeight) < 0.01f;
                            int bCorners = (bl && bt ? 1 : 0) + (br && bt ? 1 : 0) + (bl && bb ? 1 : 0) + (br && bb ? 1 : 0);
                            float bContact = (bl ? best.H : 0f) + (br ? best.H : 0f) + (bt ? best.W : 0f) + (bb ? best.W : 0f);
                            float bEdgeDist = Math.Min(best.X, PalletWidth - (best.X + best.W))
                                            + Math.Min(best.Y, PalletHeight - (best.Y + best.H));

                            bool replace = false;
                            if (cCorners != bCorners) replace = cCorners > bCorners;
                            else if (Math.Abs(cContact - bContact) > 1e-3f) replace = cContact > bContact;
                            else if (Math.Abs(cEdgeDist - bEdgeDist) > 1e-3f) replace = cEdgeDist < bEdgeDist;
                            else if (cand.Y < best.Y - 1e-3f || (Math.Abs(cand.Y - best.Y) <= 1e-3f && cand.X < best.X - 1e-3f))
                                replace = true;

                            if (replace) { best = cand; freeRectIndex = frIdx; }
                        }
                    }
                }
            }

            return best != null;
        }

        private List<RectangleF> BuildFreeRectsFromExisting(string group, int layer)
        {
            var pallet = new RectangleF(EdgeMargin, EdgeMargin,
                PalletWidth - 2 * EdgeMargin, PalletHeight - 2 * EdgeMargin);
            var freeRects = new List<RectangleF> { pallet };

            if (palletPlacements.TryGetValue(group, out var existing))
            {
                foreach (var pb in existing.Where(p => p.Layer == layer))
                {
                    var used = new RectangleF(pb.X, pb.Y, pb.Length, pb.Width); // 脚印
                                                                                // 直接用“膨胀扣除”，继承你的板间距
                    ApplySpacingToAllFreeRects(freeRects, used, InterBoardGap);
                }
            }
            return freeRects;
        }

        // 在指定层，尽可能把 toPlace 掉落进去；返回“本层放下的数量”
        private int PlaceBoardsIntoExistingLayer(string group, int layer, List<Board> toPlace)
        {
            var freeRects = BuildFreeRectsFromExisting(group, layer);
            if (freeRects.Count == 0) return 0;

            int placedCount = 0;

            // 小优化：先放大板
            toPlace.Sort((a, b) => (b.Length * b.Width).CompareTo(a.Length * a.Width));

            while (toPlace.Count > 0)
            {
                PlaceCandidate best; int frIdx;
                int bestBoardIndex = -1;

                // 找到“当前可放下”的最佳板
                for (int i = 0; i < toPlace.Count; i++)
                {
                    if (TryFindBestPlacement(freeRects, toPlace[i], out best, out frIdx))
                    {
                        bestBoardIndex = i;
                        break; // 先来一个就行，下面立即放
                    }
                }

                if (bestBoardIndex == -1) break; // 本层再也放不下

                // 最佳板再次求精（用上面统一逻辑对所有板筛一遍也可，这里为提速已提前找到一个）
                if (!TryFindBestPlacement(freeRects, toPlace[bestBoardIndex], out best, out frIdx)) break;

                // —— 落点（取整 + 钳位）——
                int px = (int)Math.Round(best.X);
                int py = (int)Math.Round(best.Y);
                int pw = (int)Math.Round(best.W);
                int ph = (int)Math.Round(best.H);
                if (px + pw > PalletWidth) px = PalletWidth - pw;
                if (py + ph > PalletHeight) py = PalletHeight - ph;

                var b = toPlace[bestBoardIndex];
                if (!palletPlacements.TryGetValue(group, out var list))
                    palletPlacements[group] = list = new List<PlacedBoard>();
                list.Add(new PlacedBoard(b.Barcode, px, py, pw, ph, best.Rotated, layer));

                // 更新 freeRects：先按未膨胀脚印切分，再把“间距禁区”扣出去
                var used = new RectangleF(best.X, best.Y, best.W, best.H);
                SubtractAndUpdateFreeRects(freeRects, frIdx, best.FreeRect, used);
                ApplySpacingToAllFreeRects(freeRects, used, InterBoardGap);

                toPlace.RemoveAt(bestBoardIndex);
                placedCount++;
            }

            return placedCount;
        }


        // 新数据到来：按组累加码垛（不打乱已放置的层与坐标）
        private void AccumulateStackForGroup(string group, List<Board> incoming, bool allowNewLayers = true)
        {
            if (incoming == null || incoming.Count == 0) return;

            lock (_lockBoards)
            {
                if (!palletPlacements.TryGetValue(group, out var existing))
                    palletPlacements[group] = existing = new List<PlacedBoard>();

                // 1) 现有层的最高层号
                int maxLayer = existing.Count == 0 ? -1 : existing.Max(p => p.Layer);

                // 2) 从第0层开始，逐层“掉落式”填补
                for (int layer = 0; layer <= maxLayer && incoming.Count > 0; layer++)
                    PlaceBoardsIntoExistingLayer(group, layer, incoming);

                // 3) 仍有剩余，则从下一层开始继续叠加（保持旧层不变）
                if (allowNewLayers && incoming.Count > 0)
                {
                    int start = maxLayer + 1;

                    // 直接复用你的整体堆叠算法，但把层号偏移
                    var newPlaced = StackBoardsForGroup(incoming); // 这会返回从 0 开始的层
                    foreach (var pb in newPlaced)
                        existing.Add(new PlacedBoard(pb.Barcode, pb.X, pb.Y, pb.Length, pb.Width, pb.Rotated, pb.Layer + start));

                    incoming.Clear();
                }
            }

            // 刷新 UI
            RefreshPalletDisplay();
        }


        private void GroupAndStackBoards()
        {
            palletPlacements.Clear();

            var groups = new List<IGrouping<string, Board>>();

            lock (_lockBoards)
            {
                groups = new List<IGrouping<string, Board>>(boards.GroupBy(b => b.Group).Take(5));
            }

            foreach (var grp in groups)
            {
                var list = grp.ToList();
                var placements = StackBoardsForGroup(list);
                palletPlacements[grp.Key] = placements;
            }

            this.BeginInvoke(new Action(() =>
            {
                UpdateLayerSelectorMax();
                tableLayoutPanel1.Invalidate();
            }));

        }


        // 将 boards（某一托盘组的板件）布局到若干层，返回该组的放置结果
        private List<PlacedBoard> StackBoardsForGroup(List<Board> boards)
        {
            var placedBoards = new List<PlacedBoard>();
            if (boards == null || boards.Count == 0) return placedBoards;

            // 先放大板，减少早期大洞
            var toPlace = boards.OrderByDescending(b => b.Length * b.Width).ToList();

            int layer = 0;
            const int maxLayers = 100; // 保险上限

            while (toPlace.Count > 0 && layer < maxLayers)
            {
                // 每层从整块托盘开始
                var freeRects = new List<RectangleF> { new RectangleF(0, 0, PalletWidth, PalletHeight) };
                var nextLayerBoards = new List<Board>();
                bool placedAnyThisLayer = false;

                foreach (var b in toPlace)
                {
                    PlaceCandidate best = null;

                    // 两种朝向（Length/Width 为脚印尺寸；Rot=true 表示原材旋转过）
                    var orientations = new[]
                    {
                new { Rot = false, L = (float)b.Length, W = (float)b.Width  },
                new { Rot = true,  L = (float)b.Width,  W = (float)b.Length }
            };

                    // 中性顺序遍历 freeRects（如需左上/右下优先，可在此处加排序）
                    var freeRectsToTry = freeRects.ToList();

                    for (int frIdxView = 0; frIdxView < freeRectsToTry.Count; frIdxView++)
                    {
                        var fr = freeRectsToTry[frIdxView];
                        int frIdx = freeRects.IndexOf(fr); // 找到真实索引用于后续切分
                        if (frIdx == -1) continue;

                        foreach (var o in orientations)
                        {
                            if (o.L > fr.Width || o.W > fr.Height) continue;

                            // 四个锚点都尝试
                            Anchor[] anchorsToTry = { Anchor.TopLeft, Anchor.TopRight, Anchor.BottomLeft, Anchor.BottomRight };

                            foreach (var anchor in anchorsToTry)
                            {
                                float px, py;
                                switch (anchor)
                                {
                                    case Anchor.TopLeft: px = fr.X; py = fr.Y; break;
                                    case Anchor.TopRight: px = fr.Right - o.L; py = fr.Y; break;
                                    case Anchor.BottomLeft: px = fr.X; py = fr.Bottom - o.W; break;
                                    default /* BottomRight*/: px = fr.Right - o.L; py = fr.Bottom - o.W; break;
                                }

                                // 必须完全落在 fr 内
                                if (px < fr.X - 0.01f || py < fr.Y - 0.01f ||
                                    px + o.L > fr.Right + 0.01f || py + o.W > fr.Bottom + 0.01f)
                                    continue;

                                // 全局托盘边界保护
                                if (px < 0 || py < 0 || px + o.L > PalletWidth || py + o.W > PalletHeight)
                                    continue;

                                var cand = new PlaceCandidate
                                {
                                    FreeRectIndex = frIdx,
                                    FreeRect = fr,
                                    Rotated = o.Rot,
                                    Anchor = anchor,
                                    X = px,
                                    Y = py,
                                    W = o.L,
                                    H = o.W
                                };
                                cand.Score = ScoreCandidate(cand); // 四边等权 + 四角加分 的评分

                                // —— 平分规则：角数 > 贴边总长度 > 距边距离(越小越好) > 更靠上/更靠左 —— //
                                if (best == null || cand.Score < best.Score - 1e-3f)
                                {
                                    best = cand;
                                }
                                else if (Math.Abs(cand.Score - best.Score) <= 1e-3f)
                                {
                                    // 计算 cand 的四边接触与占角
                                    bool cl = Math.Abs(cand.X - 0f) < 0.01f;
                                    bool ct = Math.Abs(cand.Y - 0f) < 0.01f;
                                    bool cr = Math.Abs((cand.X + cand.W) - PalletWidth) < 0.01f;
                                    bool cb = Math.Abs((cand.Y + cand.H) - PalletHeight) < 0.01f;
                                    int cCorners = (cl && ct ? 1 : 0) + (cr && ct ? 1 : 0) + (cl && cb ? 1 : 0) + (cr && cb ? 1 : 0);
                                    float cContact = (cl ? cand.H : 0f) + (cr ? cand.H : 0f) + (ct ? cand.W : 0f) + (cb ? cand.W : 0f);
                                    float cEdgeDist = Math.Min(cand.X, PalletWidth - (cand.X + cand.W))
                                                    + Math.Min(cand.Y, PalletHeight - (cand.Y + cand.H));

                                    // 计算 best 的四边接触与占角
                                    bool bl = Math.Abs(best.X - 0f) < 0.01f;
                                    bool bt = Math.Abs(best.Y - 0f) < 0.01f;
                                    bool br = Math.Abs((best.X + best.W) - PalletWidth) < 0.01f;
                                    bool bb = Math.Abs((best.Y + best.H) - PalletHeight) < 0.01f;
                                    int bCorners = (bl && bt ? 1 : 0) + (br && bt ? 1 : 0) + (bl && bb ? 1 : 0) + (br && bb ? 1 : 0);
                                    float bContact = (bl ? best.H : 0f) + (br ? best.H : 0f) + (bt ? best.W : 0f) + (bb ? best.W : 0f);
                                    float bEdgeDist = Math.Min(best.X, PalletWidth - (best.X + best.W))
                                                    + Math.Min(best.Y, PalletHeight - (best.Y + best.H));

                                    bool replace = false;
                                    if (cCorners != bCorners) replace = cCorners > bCorners;
                                    else if (Math.Abs(cContact - bContact) > 1e-3f) replace = cContact > bContact;
                                    else if (Math.Abs(cEdgeDist - bEdgeDist) > 1e-3f) replace = cEdgeDist < bEdgeDist;
                                    else if (cand.Y < best.Y - 1e-3f || (Math.Abs(cand.Y - best.Y) <= 1e-3f && cand.X < best.X - 1e-3f))
                                        replace = true;

                                    if (replace) best = cand;
                                }
                            }
                        }
                    }

                    if (best != null)
                    {
                        placedAnyThisLayer = true;

                        // —— 真正落点：取整 + 最终钳位，杜绝浮点 +1 越界 —— //
                        int px = (int)Math.Round(best.X);
                        int py = (int)Math.Round(best.Y);
                        int pw = (int)Math.Round(best.W);
                        int ph = (int)Math.Round(best.H);
                        if (px + pw > PalletWidth) px = PalletWidth - pw;
                        if (py + ph > PalletHeight) py = PalletHeight - ph;

                        placedBoards.Add(new PlacedBoard(b.Barcode, px, py, pw, ph, best.Rotated, layer));

                        var used = new RectangleF(best.X, best.Y, best.W, best.H);

                        // 先对“所在 freeRect”按未膨胀的 used 做四带切分（你现有的）
                        SubtractAndUpdateFreeRects(freeRects, best.FreeRectIndex, best.FreeRect, used);

                        // 再对“所有 freeRects”按【膨胀后的 used】扣出禁放区，确保与其它板保持间距
                        ApplySpacingToAllFreeRects(freeRects, used, InterBoardGap);

                    }
                    else
                    {
                        // 本层放不下，留待下一层/塞缝
                        nextLayerBoards.Add(b);
                    }
                }

                if (!placedAnyThisLayer)
                    break; // 本层一个也放不下，直接退出以免死循环

                // 用剩余 freeRects 对 “本层未放下的小板” 继续塞缝
                FillSmallGaps(nextLayerBoards, freeRects, placedBoards, layer, false, PalletHeight * 0.7f);

                // 进入下一层
                toPlace = nextLayerBoards;
                layer++;
            }

            return placedBoards;
        }



        // 小间隙填充方法（同步引导逻辑）
        private void FillSmallGaps(List<Board> toPlace, List<RectangleF> freeRects,
                           List<PlacedBoard> placedBoards, int layer, bool isLastFew, float bottomGuideY)
        {
            // 小板优先（从小到大补缝）
            toPlace.Sort((a, b) => (a.Length * a.Width).CompareTo(b.Length * b.Width));

            while (true)
            {
                PlaceCandidate best = null;
                Board bestBoard = null;
                int bestBoardIndex = -1;

                for (int frIdx = 0; frIdx < freeRects.Count; frIdx++)
                {
                    var fr = freeRects[frIdx];

                    for (int bi = 0; bi < toPlace.Count; bi++)
                    {
                        var b = toPlace[bi];
                        var orientations = new[]
                        {
                    new { Rot=false, L=b.Length, W=b.Width },
                    new { Rot=true,  L=b.Width,  W=b.Length }
                };

                        foreach (var o in orientations)
                        {
                            // 放不下跳过
                            if (o.L > fr.Width || o.W > fr.Height) continue;

                            // 试四个锚点（与主流程一致）
                            foreach (Anchor anchor in Enum.GetValues(typeof(Anchor)))
                            {
                                float px, py;
                                switch (anchor)
                                {
                                    case Anchor.TopLeft: px = fr.X; py = fr.Y; break;
                                    case Anchor.TopRight: px = fr.Right - o.L; py = fr.Y; break;
                                    case Anchor.BottomLeft: px = fr.X; py = fr.Bottom - o.W; break;
                                    default: /*BottomRight*/ px = fr.Right - o.L; py = fr.Bottom - o.W; break;
                                }

                                // 全局边界保护
                                if (px < 0 || py < 0 || px + o.L > PalletWidth || py + o.W > PalletHeight)
                                    continue;

                                var cand = new PlaceCandidate
                                {
                                    FreeRectIndex = frIdx,
                                    FreeRect = fr,
                                    Rotated = o.Rot,
                                    Anchor = anchor,
                                    X = px,
                                    Y = py,
                                    W = o.L,
                                    H = o.W
                                };
                                cand.Score = ScoreCandidate(cand);

                                if (best == null || cand.Score < best.Score)
                                {
                                    best = cand;
                                    bestBoard = b;
                                    bestBoardIndex = bi;
                                }
                            }
                        }
                    }
                }

                if (best == null) break; // 再也塞不进了

                // ---- 真正放置：钳位 + 四带切分 ----
                int x = (int)Math.Round(best.X);
                int y = (int)Math.Round(best.Y);
                int w = (int)Math.Round(best.W);
                int h = (int)Math.Round(best.H);
                if (x + w > PalletWidth) x = PalletWidth - w;    // 钳位，避免 +1 越界
                if (y + h > PalletHeight) y = PalletHeight - h;

                placedBoards.Add(new PlacedBoard(bestBoard.Barcode, x, y, w, h, best.Rotated, layer));

                var used = new RectangleF(best.X, best.Y, best.W, best.H);
                SubtractAndUpdateFreeRects(freeRects, best.FreeRectIndex, best.FreeRect, used);
                ApplySpacingToAllFreeRects(freeRects, used, InterBoardGap); 


                toPlace.RemoveAt(bestBoardIndex);
            }
        }




        // 保持MergeFreeRects和IsAdjacentAndMergeable
        private void MergeFreeRects(List<RectangleF> freeRects)
        {
            bool mergedAny;
            do
            {
                mergedAny = false;
                for (int i = 0; i < freeRects.Count; i++)
                {
                    var r1 = freeRects[i];
                    for (int j = i + 1; j < freeRects.Count; j++)
                    {
                        var r2 = freeRects[j];
                        if (IsAdjacentAndMergeable(r1, r2, out RectangleF merged))
                        {
                            freeRects[i] = merged;
                            freeRects.RemoveAt(j);
                            mergedAny = true;
                            break;
                        }
                    }
                    if (mergedAny) break;
                }
            } while (mergedAny);
        }

        private bool IsAdjacentAndMergeable(RectangleF r1, RectangleF r2, out RectangleF merged)
        {
            merged = RectangleF.Empty;

            // 横向合并（高度相等，上下对齐，左右相邻）
            if (Math.Abs(r1.Y - r2.Y) < 0.01f && Math.Abs(r1.Height - r2.Height) < 0.01f)
            {
                if (Math.Abs(r1.Right - r2.Left) < 0.01f || Math.Abs(r2.Right - r1. Left) < 0.01f)
                {
                    float x = Math.Min(r1.X, r2.X);
                    float width = r1.Width + r2.Width;
                    merged = new RectangleF(x, r1.Y, width, r1.Height);
                    return true;
                }
            }

            // 纵向合并（宽度相等，左右对齐，上下相邻）
            if (Math.Abs(r1.X - r2.X) < 0.01f && Math.Abs(r1.Width - r2.Width) < 0.01f)
            {
                if (Math.Abs(r1.Bottom - r2.Top) < 0.01f || Math.Abs(r2.Bottom - r1.Top) < 0.01f)
                {
                    float y = Math.Min(r1.Y, r2.Y);
                    float height = r1.Height + r2.Height;
                    merged = new RectangleF(r1.X, y, r1.Width, height);
                    return true;
                }
            }

            return false;
        }



        // 1. 修正托盘面板绘制时的缩放比例计算
        private void PalletPanel_Paint(object sender, PaintEventArgs e)
        {
            Panel pnl = sender as Panel;
            string group = pnl.Tag as string;

            if (!palletPlacements.ContainsKey(group))
                return;

            var boards = palletPlacements[group];
            var g = e.Graphics;

            g.Clear(Color.White);
            g.DrawRectangle(Pens.Black, 0, 0, pnl.Width - 1, pnl.Height - 1);

            // 缩放比例
            float targetAspectRatio = (float)PalletWidth / PalletHeight;
            float panelAspectRatio = (float)pnl.Width / pnl.Height;
            float scale = panelAspectRatio >= targetAspectRatio
                ? pnl.Height / (float)PalletHeight
                : pnl.Width / (float)PalletWidth;
            float scaleX = scale;
            float scaleY = scale;

            // 绘制托盘坐标刻度
            using (Font font = new Font("Arial", 7))
            using (Pen dashPen = new Pen(Color.LightGray) { DashStyle = System.Drawing.Drawing2D.DashStyle.Dot })
            {
                // X方向刻度（每500mm画一条虚线）
                for (int x = 0; x <= PalletWidth; x += 500)
                {
                    float px = x * scaleX;
                    g.DrawLine(dashPen, px, 0, px, pnl.Height);
                    g.DrawString($"{x}mm", font, Brushes.Gray, px + 2, 2);
                }
                // Y方向刻度（每500mm画一条虚线）
                for (int y = 0; y <= PalletHeight; y += 500)
                {
                    float py = y * scaleY;
                    g.DrawLine(dashPen, 0, py, pnl.Width, py);
                    g.DrawString($"{y}mm", font, Brushes.Gray, 2, py + 2);
                }
            }

            // 绘制板件
            if (showAllLayers)
            {
                int maxLayer = boards.Max(b => b.Layer);
                for (int layer = 0; layer <= maxLayer; layer++)
                {
                    byte alpha = (byte)(50 + 205 * (maxLayer - layer) / (maxLayer + 1));
                    foreach (var pb in boards.Where(b => b.Layer == layer))
                    {
                        DrawPlacedBoard(g, pb, scaleX, scaleY, alpha);
                    }
                }
            }
            else
            {
                foreach (var pb in boards.Where(b => b.Layer == currentDisplayLayer))
                {
                    DrawPlacedBoard(g, pb, scaleX, scaleY, 255);
                }
            }
        }

        // 用“脚印尺寸”（已考虑旋转后的尺寸）绘制；不要再根据 Rotated 交换
        private void DrawPlacedBoard(Graphics g, PlacedBoard pb, float scaleX, float scaleY, byte alpha)
        {
            float x = pb.X * scaleX;
            float y = pb.Y * scaleY;
            float w = pb.Length * scaleX; // ← length 始终指 X 方向尺寸（落地后的）
            float h = pb.Width * scaleY; // ← width  始终指 Y 方向尺寸（落地后的）

            // 颜色
            int r = Math.Abs(pb.Barcode.GetHashCode() * 997) % 256;
            int gr = Math.Abs(pb.Barcode.GetHashCode() * 991) % 256;
            int b = Math.Abs(pb.Barcode.GetHashCode() * 983) % 256;
            using (Brush brush = new SolidBrush(Color.FromArgb(alpha, r, gr, b)))
                g.FillRectangle(brush, x, y, w, h);
            g.DrawRectangle(Pens.Black, x, y, w, h);

            // 角点标注：同样用脚印尺寸
            DrawBoardCornerCoords(g, pb.X, pb.Y, pb.Length, pb.Width, scaleX, scaleY);
        }




        // 绘制四个角的实际坐标（mm）
        private void DrawBoardCornerCoords(Graphics g, float startX, float startY, float length, float width, float scaleX, float scaleY)
        {
            using (Font font = new Font("Arial", 6))
            using (Brush brush = new SolidBrush(Color.Black))
            {
                g.DrawString($"({startX},{startY})", font, brush, startX * scaleX + 2, startY * scaleY + 2);
                g.DrawString($"({startX + length},{startY})", font, brush, (startX + length) * scaleX + 2, startY * scaleY + 2);
                g.DrawString($"({startX},{startY + width})", font, brush, startX * scaleX + 2, (startY + width) * scaleY - 10);
                g.DrawString($"({startX + length},{startY + width})", font, brush, (startX + length) * scaleX + 2, (startY + width) * scaleY - 10);
            }
        }




        private void UpdateLayerSelectorMax()
        {
            if (palletPlacements.Count == 0) return;

            int maxLayer = palletPlacements.Values
                .SelectMany(list => list)
                .Max(pb => pb.Layer);

            nudLayer.Maximum = Math.Max(maxLayer, 0);
            if (currentDisplayLayer > nudLayer.Maximum)
            {
                currentDisplayLayer = (int)nudLayer.Maximum;
                nudLayer.Value = nudLayer.Maximum;
            }
        }



        private void PalletPanel_Click(object sender, EventArgs e)
        {
            Panel pnl = sender as Panel;
            string group = pnl.Tag as string;

            if (palletPlacements.TryGetValue(group, out var boards))
            {
                string info = $"托盘组 {group} 共有 {boards.Count} 块板件：\n\n";
                foreach (var b in boards)
                {
                    info += $"Barcode: {b.Barcode}, 位置:({b.X},{b.Y}), 尺寸: {b.Length}x{b.Width}, 旋转: {(b.Rotated ? "是" : "否")}\n";
                }

                MessageBox.Show(info, "码垛详细信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            base.OnFormClosing(e);
            try
            {
                if (listener != null && listener.IsListening)
                {
                    listener.Stop();
                    listener.Close();
                }
            }
            catch { }
        }

        private void palletPanel_MouseMove(object sender, MouseEventArgs e)
        {
            var pnl = (Panel)sender;

            // 与绘制一致的缩放计算
            float targetAspect = (float)PalletWidth / PalletHeight;
            float panelAspect = (float)pnl.Width / pnl.Height;
            float scale = panelAspect >= targetAspect
                ? pnl.Height / (float)PalletHeight
                : pnl.Width / (float)PalletWidth;

            // 像素 -> 毫米
            float mmX = e.X / scale;
            float mmY = e.Y / scale;

            lblMousePos.Text = $"X: {mmX:F1} mm, Y: {mmY:F1} mm";
        }

        // 在 Form1 类里（任意合适位置）新增：

        // 四个锚点
        private enum Anchor { TopLeft, TopRight, BottomLeft, BottomRight }

        private sealed class PlaceCandidate
        {
            public int FreeRectIndex;
            public RectangleF FreeRect;
            public bool Rotated;
            public Anchor Anchor;
            public float X, Y, W, H;
            public float Score;
        }

        // —— 统一权重（可按需要微调）——
        const float EDGE_CONTACT_WEIGHT = 2.5f;  // 接触长度的权重（四边相同）
        const float CORNER_TOUCH_BONUS = 1200f; // 占角奖励（四角相同）
        const float EDGE_DISTANCE_WEIGHT = 0.01f; // 离最近边的距离惩罚（很小，毫米级）

        private float ScoreCandidate(PlaceCandidate c)
        {
            // 1) 基础匹配度：短边更贴合优先，其次长边
            float dw = c.FreeRect.Width - c.W;   // >= 0
            float dh = c.FreeRect.Height - c.H;   // >= 0
            float shortSideFit = Math.Min(dw, dh);
            float longSideFit = Math.Max(dw, dh);
            float baseScore = shortSideFit * 10000f + longSideFit;

            // 2) 与托盘四条边的接触（四边等权）
            bool touchLeft = Math.Abs(c.X - 0f) < 0.01f;
            bool touchTop = Math.Abs(c.Y - 0f) < 0.01f;
            bool touchRight = Math.Abs((c.X + c.W) - PalletWidth) < 0.01f;
            bool touchBottom = Math.Abs((c.Y + c.H) - PalletHeight) < 0.01f;

            float contact =
                (touchLeft ? c.H : 0f) +
                (touchRight ? c.H : 0f) +
                (touchTop ? c.W : 0f) +
                (touchBottom ? c.W : 0f);

            // 3) 占角奖励（四个角都算）
            int corners =
                (touchLeft && touchTop ? 1 : 0) +
                (touchRight && touchTop ? 1 : 0) +
                (touchLeft && touchBottom ? 1 : 0) +
                (touchRight && touchBottom ? 1 : 0);

            // 4) 离最近边距离（越小越好；只作很轻微的罚分）
            float distX = Math.Min(c.X, PalletWidth - (c.X + c.W));
            float distY = Math.Min(c.Y, PalletHeight - (c.Y + c.H));
            float edgeDistance = distX + distY;

            // 分数越小越好
            return baseScore
                   - EDGE_CONTACT_WEIGHT * contact
                   - CORNER_TOUCH_BONUS * corners
                   + EDGE_DISTANCE_WEIGHT * edgeDistance;
        }



        // 把 used 从 fr 扣掉：得到左/右/上/下四条无重叠剩余带
        private void SubtractAndUpdateFreeRects(List<RectangleF> freeRects, int frIndex, RectangleF fr, RectangleF used)
        {
            var remain = new List<RectangleF>();

            // 左
            if (used.X > fr.X)
            {
                var left = new RectangleF(fr.X, used.Y, used.X - fr.X, used.Height);
                if (left.Width > 0.1f && left.Height > 0.1f) remain.Add(left);
            }
            // 右
            if (used.Right < fr.Right)
            {
                var right = new RectangleF(used.Right, used.Y, fr.Right - used.Right, used.Height);
                if (right.Width > 0.1f && right.Height > 0.1f) remain.Add(right);
            }
            // 上
            if (used.Y > fr.Y)
            {
                var top = new RectangleF(used.X, fr.Y, used.Width, used.Y - fr.Y);
                if (top.Width > 0.1f && top.Height > 0.1f) remain.Add(top);
            }
            // 下
            if (used.Bottom < fr.Bottom)
            {
                var bottom = new RectangleF(fr.X, used.Bottom, fr.Width, fr.Bottom - used.Bottom);
                if (bottom.Width > 0.1f && bottom.Height > 0.1f) remain.Add(bottom);
            }

            freeRects.RemoveAt(frIndex);
            freeRects.AddRange(remain);
            RemoveContainedRects(freeRects);
            MergeFreeRects(freeRects); // 你原有的相邻合并
        }

        // 去除被包含（MaxRects惯用）
        private void RemoveContainedRects(List<RectangleF> freeRects)
        {
            for (int i = 0; i < freeRects.Count; i++)
            {
                var a = freeRects[i];
                for (int j = freeRects.Count - 1; j >= 0; j--)
                {
                    if (i == j) continue;
                    var b = freeRects[j];
                    if (a.Contains(b))
                    {
                        freeRects.RemoveAt(j);
                        if (j < i) i--;
                    }
                    else if (b.Contains(a))
                    {
                        freeRects.RemoveAt(i);
                        i--;
                        break;
                    }
                }
            }
        }


        private static RectangleF InflateRect(RectangleF r, float delta)
        {
            return new RectangleF(r.X - delta, r.Y - delta, r.Width + 2 * delta, r.Height + 2 * delta);
        }

        private static RectangleF IntersectRect(RectangleF a, RectangleF b)
        {
            float x1 = Math.Max(a.Left, b.Left);
            float y1 = Math.Max(a.Top, b.Top);
            float x2 = Math.Min(a.Right, b.Right);
            float y2 = Math.Min(a.Bottom, b.Bottom);
            return (x2 <= x1 || y2 <= y1) ? RectangleF.Empty : new RectangleF(x1, y1, x2 - x1, y2 - y1);
        }

        // 从“所有 freeRects”中扣掉一个 used（可带膨胀/裁剪）
        private void ApplySpacingToAllFreeRects(List<RectangleF> freeRects, RectangleF used, float gap)
        {
            float d = gap / 2f;

            // 先对 used 做膨胀，再裁到托盘内（这样贴边时不会要求与边留间距）
            var pallet = new RectangleF(EdgeMargin, EdgeMargin,
                                        PalletWidth - 2 * EdgeMargin, PalletHeight - 2 * EdgeMargin);
            var inflated = IntersectRect(InflateRect(used, d), pallet);
            if (inflated.IsEmpty) return;

            for (int i = 0; i < freeRects.Count; i++)
            {
                var fr = freeRects[i];
                var cut = IntersectRect(fr, inflated);
                if (cut.IsEmpty) continue;

                // 四带切分：top / bottom / left / right
                var pieces = new List<RectangleF>();

                var top = new RectangleF(fr.X, fr.Y, fr.Width, cut.Y - fr.Y);
                var bottom = new RectangleF(fr.X, cut.Bottom, fr.Width, fr.Bottom - cut.Bottom);
                var left = new RectangleF(fr.X, cut.Y, cut.X - fr.X, cut.Height);
                var right = new RectangleF(cut.Right, cut.Y, fr.Right - cut.Right, cut.Height);

                if (top.Width > 0.1f && top.Height > 0.1f) pieces.Add(top);
                if (bottom.Width > 0.1f && bottom.Height > 0.1f) pieces.Add(bottom);
                if (left.Width > 0.1f && left.Height > 0.1f) pieces.Add(left);
                if (right.Width > 0.1f && right.Height > 0.1f) pieces.Add(right);

                freeRects.RemoveAt(i);
                freeRects.InsertRange(i, pieces);
                i += pieces.Count - 1;
            }

            RemoveContainedRects(freeRects);
            MergeFreeRects(freeRects);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            palletPlacements.Clear();
            RefreshDataGridView();
            RefreshPalletDisplay();
        }


        private List<PanelOut> CollectPanelsFromGrid()
        {
            var list = new List<PanelOut>();
            var g = dgvStackInfo;

            // 小工具：按列名/表头找列
            int Col(params string[] keys)
            {
                for (int i = 0; i < g.Columns.Count; i++)
                {
                    string name = g.Columns[i].Name?.Trim() ?? "";
                    string head = g.Columns[i].HeaderText?.Trim() ?? "";
                    foreach (var k in keys)
                    {
                        if (string.Equals(name, k, StringComparison.OrdinalIgnoreCase) ||
                            string.Equals(head, k, StringComparison.OrdinalIgnoreCase))
                            return i;
                    }
                }
                return -1;
            }

            int cOrder = Col("Barcode", "条码", "Name");
            int cCX = Col("X", "中心X", "x");
            int cCY = Col("Y", "中心Y", "y");
            int cLength = Col("Length", "长度");
            int cWidth = Col("Width", "宽度");
            int cHeight = Col("Height", "厚度", "高度")+8;     // 可选
            int cLayer = Col("Layer", "层数");
            int cRotated = Col("Rotated", "旋转");
            int cPlace = Col("Placement", "放置位置");     // 可选

            // 逐行读取
            foreach (DataGridViewRow r in g.Rows)
            {
                if (r.IsNewRow) continue;

                // 读取并容错
                string name = Convert.ToString(r.Cells[cOrder >= 0 ? cOrder : 0].Value)?.Trim() ?? "";
                double cx = double.TryParse(Convert.ToString(r.Cells[cCX].Value), out var tmpCx) ? tmpCx : 0;
                double cy = double.TryParse(Convert.ToString(r.Cells[cCY].Value), out var tmpCy) ? tmpCy : 0;
                int length = int.TryParse(Convert.ToString(r.Cells[cLength].Value), out var tmpL) ? tmpL : 0;
                int width = int.TryParse(Convert.ToString(r.Cells[cWidth].Value), out var tmpW) ? tmpW : 0;
                int layer = int.TryParse(Convert.ToString(r.Cells[cLayer].Value), out var tmpLayer) ? tmpLayer : 0;

                double height = DefaultBoardHeight;

                bool rotate = false;
                if (cRotated >= 0)
                {
                    var rotStr = Convert.ToString(r.Cells[cRotated].Value)?.Trim();
                    rotate = rotStr == "是" || rotStr?.Equals("true", StringComparison.OrdinalIgnoreCase) == true ||
                             rotStr?.Equals("yes", StringComparison.OrdinalIgnoreCase) == true;
                }

                int placement = 0;
                if (cPlace >= 0 && int.TryParse(Convert.ToString(r.Cells[cPlace].Value), out var tmpP))
                    placement = tmpP;

                var p = new PanelOut
                {
                    Name = name,
                    ImmutableLength = length,     // 若有原始未旋转尺寸，可替换成原值
                    ImmutableWidth = width,      // 同上
                    Length = length,
                    Width = width,
                    Height = height,
                    PalletizingHeight = (height+5) * layer,  // 以层数近似计算；若有精准值可替换
                    PositionX = cx,  // 中心X
                    PositionY = cy,  // 中心Y
                    IsAnalyzed = false,
                    Diagonal = 0,
                    Layer = layer,
                    IsFirst = false,
                    Rotate = rotate,
                    Placement = placement,
                    StackingTip = "",
                    ManualLeveling = 0,

                    // 条子相关默认
                    IsBottomPlate = false,
                    StackNo = 0,
                    BoardType = 0
                };

                list.Add(p);
            }

            return list;
        }

        private void btnExportJson_Click_1(object sender, EventArgs e)
        {
            var panels = CollectPanelsFromGrid();
            if (panels.Count == 0)
            {
                MessageBox.Show("表格没有可导出的数据。");
                return;
            }

            var sfd = new SaveFileDialog
            {
                Filter = "JSON 文件 (*.json)|*.json",
                FileName = $"panels_{DateTime.Now:yyyyMMdd_HHmmss}.json"
            };
            if (sfd.ShowDialog() != DialogResult.OK) return;

            var opts = new JsonSerializerOptions
            {
                WriteIndented = true,
                Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
            };

            string json = JsonSerializer.Serialize(panels, opts);
            File.WriteAllText(sfd.FileName, json, Encoding.UTF8);

            MessageBox.Show($"已导出 {panels.Count} 条到：\n{sfd.FileName}", "导出成功",
                MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        private void btnSendJson_Click(object sender, EventArgs e)
        {
            var panels = CollectPanelsFromGrid();
            if (panels.Count == 0)
            {
                MessageBox.Show("表格没有可导出的数据。");
                return;
            }

            var sfd = new SaveFileDialog
            {
                Filter = "JSON 文件 (*.json)|*.json",
                FileName = $"panels_{DateTime.Now:yyyyMMdd_HHmmss}.json"
            };
            if (sfd.ShowDialog() != DialogResult.OK) return;

            var opts = new JsonSerializerOptions
            {
                WriteIndented = true,
                Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
            };

            string json = JsonSerializer.Serialize(panels, opts);
            //File.WriteAllText(sfd.FileName, json, Encoding.UTF8);
            //httppost:http://127.0.0.1:7000/ShowModel
            HttpPost(json, "http://192.168.31.41:7000/ShowModel");

            MessageBox.Show($"已发送 {panels.Count} 条到：\n{sfd.FileName}", "导出成功",
                MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        private void HttpPost(string json, string url)
        {
            try
            {
                var client = new HttpClient();
                var content = new StringContent(json, Encoding.UTF8, "application/json");
                var response = client.PostAsync(url, content).Result;
                if (response.IsSuccessStatusCode)
                {
                    var result = response.Content.ReadAsStringAsync().Result;
                    Console.WriteLine(result);
                }
                else
                {
                    Console.WriteLine("请求失败，状态码：{0}", response.StatusCode);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("请求失败：{0}", ex.Message);
            }
        }
    }
    // 与 PalletizationLib.Model.Panel 字段同名，便于 JSON 对接
    public sealed class PanelOut
    {
        public string Name { get; set; }
        public double ImmutableLength { get; set; }
        public double ImmutableWidth { get; set; }
        public double Length { get; set; }
        public double Width { get; set; }
        public double Height { get; set; }
        public double PalletizingHeight { get; set; }
        public double PositionX { get; set; }  // 中心X
        public double PositionY { get; set; }  // 中心Y
        public bool IsAnalyzed { get; set; }
        public double Diagonal { get; set; }
        public int Layer { get; set; }
        public bool IsFirst { get; set; }
        public bool Rotate { get; set; }
        public int Placement { get; set; }
        public string StackingTip { get; set; } = "";
        public int? ManualLeveling { get; set; } = 0;

        // 条子相关（若没有真实值就给默认）
        public bool IsBottomPlate { get; set; }
        public int StackNo { get; set; }
        public int BoardType { get; set; }  // 若你有枚举，可改成相应枚举类型
    }

    public class Board
    {
        public string Barcode { get; set; }
        public int Length { get; set; }
        public int Width { get; set; }
        public string Group { get; set; }

        public Board() { }

        public Board(string order, int length, int width, string group)
        {
            Barcode = order;
            Length = length;
            Width = width;
            Group = group;
        }
    }


    public class PlacedBoard
    {
        public string Barcode { get; }
        public int X { get; }
        public int Y { get; }
        public int Length { get; }
        public int Width { get; }
        public bool Rotated { get; }
        public int Layer { get; }

        public PlacedBoard(string order, int x, int y, int length, int width, bool rotated, int layer)
        {
            Barcode = order;
            X = x;
            Y = y;
            Length = length;
            Width = width;
            Rotated = rotated;
            Layer = layer;
        }
    }

    public class SendPanelInfo
    {
        public String Barcode { get; set; }
        public decimal Length { get; set; }
        public decimal Width { get; set; }
        public decimal Height { get; set; }

        public int Placement { get; set; }

    }



}
